use serde_json::{json, Value};
use std::cmp::Ordering;
use std::collections::HashMap;

// game data
pub struct Game {
    answer: String,
    guess: Vec<String>,
}

impl Clone for Game {
    fn clone(&self) -> Self {
        Self {
            answer: self.answer.clone(),
            guess: self.guess.clone(),
        }
    }
}

impl Game {
    // construct new game data from scratch
    pub const fn new() -> Self {
        Self {
            answer: String::new(),
            guess: Vec::new(),
        }
    }

    // construct new game data from answer string
    pub fn from_answer(answer: String) -> Self {
        Self {
            answer: answer,
            guess: Vec::new(),
        }
    }

    // add guess to game data
    pub fn add_guess(&mut self, guess: String) {
        self.guess.push(guess);
    }

    // get guess count
    pub fn get_guess_count(&mut self) -> usize {
        self.guess.len()
    }
}

// game state
pub struct State {
    total_rounds: usize,
    games: Vec<Game>,
}

impl Clone for State {
    fn clone(&self) -> Self {
        Self {
            total_rounds: self.total_rounds,
            games: self.games.clone(),
        }
    }
}

impl State {
    // construct new game state
    pub const fn new() -> Self {
        Self {
            total_rounds: 0,
            games: Vec::new(),
        }
    }

    // parse string to game state
    pub fn parse(str: &String) -> Self {
        let mut state = Self::new();
        if let Value::Object(map) = serde_json::from_str(str).expect("ERROR: FAILED TO PARSE JSON")
        {
            for (key, value) in map {
                match &key as &str {
                    "total_rounds" => {
                        if let Value::Number(number) = value {
                            state.total_rounds =
                                number.as_u64().expect("ERROR: WRONG JSON FORMAT") as usize;
                        } else {
                            panic!("ERROR: WRONG JSON FORMAT");
                        }
                    }
                    "games" => {
                        if let Value::Array(vec) = value {
                            for value in vec {
                                let mut ans: String = String::new();
                                let mut guess: Vec<String> = Vec::new();
                                if let Value::Object(map) = value {
                                    for (key, value) in map {
                                        match &key as &str {
                                            "answer" => {
                                                if let Value::String(str) = value {
                                                    ans = str;
                                                } else {
                                                    panic!("ERROR: WRONG JSON FORMAT");
                                                }
                                            }
                                            "guesses" => {
                                                if let Value::Array(vec) = value {
                                                    guess = vec
                                                        .iter()
                                                        .map(|value| {
                                                            if let Value::String(str) = value {
                                                                str.clone()
                                                            } else {
                                                                panic!("ERROR: WRONG JSON FORMAT");
                                                            }
                                                        })
                                                        .collect();
                                                } else {
                                                    panic!("ERROR: WRONG JSON FORMAT");
                                                }
                                            }
                                            _ => panic!("ERROR: WRONG JSON FORMAT"),
                                        }
                                    }
                                    state.add_game(Game {
                                        answer: ans,
                                        guess: guess,
                                    })
                                } else {
                                    panic!("ERROR: WRONG JSON FORMAT");
                                }
                            }
                        } else {
                            panic!("ERROR: WRONG JSON FORMAT");
                        }
                    }
                    _ => panic!("ERROR: WRONG JSON FORMAT"),
                }
            }
        } else {
            panic!("ERROR: WRONG JSON FORMAT");
        }
        if state.total_rounds != state.games.len() {
            panic!("ERROR: WRONG LENGTH JSON FORMAT");
        }
        state
    }

    // add a game to game state
    pub fn add_game(&mut self, game: Game) {
        self.total_rounds += 1;
        self.games.push(game);
    }

    // serialize game state
    pub fn stringify(&self) -> String {
        serde_json::to_string(&json!({
            "total_rounds":self.total_rounds,
            "games":self.games.iter().map(|game| json!({
                "answer":game.answer,
                "guesses":game.guess.iter().map(|guess| json!(guess)).collect::<Vec<Value>>()
            })).collect::<Vec<Value>>()
        }))
        .expect("ERROR: FAIL TO WRITE JSON FORMAT")
    }
}

pub const MAX_FREQUENT_WORDS: usize = 5;

// game statistics
pub struct Stat {
    win: usize,
    lose: usize,
    total_attempt: usize,
    used_words: HashMap<String, usize>,
}

impl Stat {
    // construct new game statistics
    pub fn new() -> Self {
        Self {
            win: 0,
            lose: 0,
            total_attempt: 0,
            used_words: HashMap::new(),
        }
    }

    // add a guess to game statistics
    pub fn add_guess(&mut self, guess: String) {
        self.used_words
            .entry(guess)
            .and_modify(|cnt| *cnt += 1)
            .or_insert(1);
    }

    // add a won game to game statistics
    pub fn add_win(&mut self, attempt: usize) {
        self.win += 1;
        self.total_attempt += attempt;
    }

    // add a lost game to game statistics
    pub fn add_lose(&mut self) {
        self.lose += 1;
    }

    // load game state to game statistics
    pub fn load_state(state: &State) -> Self {
        let mut stat: Self = Self::new();
        for game in &state.games {
            if !game.guess.is_empty() {
                for guess in &game.guess {
                    stat.add_guess(guess.clone());
                }
                if *game.guess.iter().rev().next().unwrap() == game.answer {
                    stat.add_win(game.guess.len());
                } else {
                    stat.add_lose();
                }
            }
        }
        stat
    }

    // get number of won games
    pub fn win(&self) -> usize {
        self.win
    }

    // get number of lost games
    pub fn lose(&self) -> usize {
        self.lose
    }

    // get total number of attempts in won games
    pub fn total_attempt(&self) -> usize {
        self.total_attempt
    }

    // get average number of attempts in won games
    pub fn average_attempt(&self) -> f64 {
        (self.total_attempt as f64) / (self.win as f64)
    }

    // get most frequently guessed words
    pub fn most_frequent_words(&self) -> Vec<(String, usize)> {
        let mut used_words_list: Vec<(String, usize)> = self
            .used_words
            .iter()
            .map(|(a, b)| (a.clone(), *b))
            .collect();
        used_words_list.sort_unstable_by(|(s1, c1), (s2, c2)| match c1.cmp(&c2) {
            Ordering::Less => Ordering::Greater,
            Ordering::Greater => Ordering::Less,
            Ordering::Equal => s1.cmp(&s2),
        });
        used_words_list.truncate(MAX_FREQUENT_WORDS);
        used_words_list
    }
}
